#region license
/*
 * Copyright (C) 2007 CchenSoft.com
 * 
 * This library is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License 2.1 or later, as
 * published by the Free Software Foundation. See the included License.txt
 * or http://www.gnu.org/copyleft/lesser.html for details.
 * 
 * Author: Billy Zhang
 */
#endregion

using System;
using System.Collections;

using Quartz;
using Quartz.Impl;
using log4net;

using CchenSoft.Workflow.Spi;
using CchenSoft.Workflow.Timer;

namespace CchenSoft.Workflow.Util
{

    /**
     * Schedules a job in the Quartz job scheduler to be executed one or more times in the future.
     * The following arguments are required:
     *
     * <ul>
     *  <li> triggerId - the id of the trigger function defined in the XML workflow
     *  <li> jobName - the name to be given to the job
     *  <li> triggerName - the name to be given to the trigger
     *  <li> groupName - the group given to both the job and the trigger
     * </ul>
     *
     * The following arguments are optional:
     * <ul>
     *  <li> username - the system account's username that will execute the function in the future.
     * If this is not specified value from WorkflowContext.getCaller() is used
     *  <li> password - the system account's password
     *  <li> local - if set to the true, a LocalWorkflowJob is used, bypassing the need for SOAP support.
     * Will be ignored if "workflowClass" is specified.
     * <li> jobClass - the class implementing 'Job' to run, defaults to WorkflowJob. If not specified,
     * defaults to either a WorkflowJob or a LocalWorkflowJob if "local" is set to true.
     *  <li>schedulerName - the name of an existing scheduler to use</li>
     *  <li>schdulerStart - if "true", start the scheduler if it hasn't been started already</li>
     *  <li>txHack - set this to true if you are getting lockups while running with transactions (defaults to false)</li>
     * </ul>
     *
     * If you are using a cron trigger, the following is required:
     * <ul>
     *  <li> cronExpression - the Cron expression
     * </ul>
     *
     * If you are using a simple trigger, the follow are all optional:
     * <ul>
     *  <li> startOffset - the offset, in milliseconds, from the current time. (default is 0)
     *  <li> endOffset - the offset, in milliseconds, from the current time. (default is infinity)
     *  <li> repeat - the repeat count (default is 0). Can also be REPEAT_INDEFINITELY
     *  <li> repeatDelay - the time delay, in milliseconds, between repeats (default is 0)
     * </ul>
     */
    public class ScheduleJob : IFunctionProvider
    {
        //~ Static fields/initializers /////////////////////////////////////////////

        private static ILog log = LogManager.GetLogger(typeof(ScheduleJob));

        //~ Methods ////////////////////////////////////////////////////////////////

        public void Execute(IDictionary transientVars, IDictionary args)
        {
            try
            {
                IWorkflowEntry entry = (IWorkflowEntry)transientVars["entry"];
                IWorkflowContext context = (IWorkflowContext)transientVars["context"];

                log.Info("Starting to schdule job for WF #" + entry.Id);

                int triggerId = Convert.ToInt32((string)args["triggerId"]);
                string jobName = (string)args["jobName"];
                string triggerName = (string)args["triggerName"];
                string groupName = (string)args["groupName"];

                string username = (string)args["username"];
                string password = (string)args["password"];

                bool txHack = Convert.ToBoolean((string)args["txHack"]);

                if (username == null)
                {
                    username = context.Caller;
                }

                string cronExpression = (string)args["cronExpression"];

                jobName = jobName + ":" + entry.Id;
                triggerName = triggerName + ":" + entry.Id;
                groupName = groupName + ":" + entry.Id;

                string schedulerName = (string)args["schedulerName"];
                IScheduler s;

                ISchedulerFactory factory = new StdSchedulerFactory();

                if ((schedulerName == null) || "".Equals(schedulerName.Trim()))
                {
                    s = factory.GetScheduler();
                }
                else
                {
                    s = factory.GetScheduler(schedulerName);
                }

                if (Convert.ToBoolean((string)args["schedulerStart"]))
                {
                    log.Info("Starting Quartz Job Scheduler");
                    s.Start();
                }

                Type jobClass;
                string jobClassArg = (string)args["jobClass"];

                if (jobClassArg != null)
                {
                    jobClass = Type.GetType(jobClassArg);
                }
                else
                {
                    jobClass = typeof(WorkflowJob);
                }

                JobDetail jobDetail = new JobDetail(jobName, groupName, jobClass);
                Trigger trigger;

                if (cronExpression == null)
                {
                    long now = DateTime.Now.Ticks;

                    // get start date - default is now
                    DateTime startDate = DateTime.Now;

                    try
                    {
                        string start = (string)args["startOffset"];

                        if (s != null)
                        {
                            startDate = new DateTime(now + Convert.ToInt64(start));
                        }
                    }
                    catch (FormatException e)
                    {
                    }

                    // get end date - default is null
                    DateTime? endDate = null;

                    try
                    {
                        string end = (string)args["endOffset"];

                        if (s != null)
                        {
                            endDate = new DateTime(now + Convert.ToInt64(end));
                        }
                    }
                    catch (FormatException e)
                    {
                    }

                    // get the repeat amount - default is 0
                    int repeat = 0;

                    try
                    {
                        string r = (string)args["repeat"];

                        if (r != null)
                        {
                            if (r.ToUpper().Equals("REPEAT_INDEFINITELY"))
                            {
                                repeat = SimpleTrigger.RepeatIndefinitely;
                            }
                            else
                            {
                                repeat = Convert.ToInt32(r);
                            }
                        }
                    }
                    catch (FormatException e)
                    {
                    }

                    // get repeat delay - default is 0
                    long delay = 0;

                    try
                    {
                        string rd = (string)args["repeatDelay"];

                        if (rd != null)
                        {
                            delay = Convert.ToInt64(rd);
                        }
                    }
                    catch (FormatException e)
                    {
                    }

                    trigger = new SimpleTrigger(triggerName, groupName, jobName, groupName, startDate, endDate, repeat, new TimeSpan(delay));
                }
                else
                {
                    trigger = new CronTrigger(triggerName, groupName, jobName, groupName, cronExpression);
                }

                JobDataMap dataMap = new JobDataMap();
                dataMap.Add("triggerId", triggerId);
                dataMap.Add("entryId", entry.Id);
                dataMap.Add("username", username);
                dataMap.Add("password", password);

                if (Convert.ToBoolean((string)args["local"]))
                {
                    dataMap.Add("configuration", transientVars["configuration"]);
                }

                jobDetail.JobDataMap = dataMap;
                jobDetail.Durable = true;

                trigger.JobName = jobDetail.Name;
                trigger.JobGroup = jobDetail.Group;

                if (txHack && !s.IsJobGroupPaused(jobDetail.Group) && !s.IsShutdown)
                {
                    s.PauseJobGroup(jobDetail.Group);

                    try
                    {
                        s.AddJob(jobDetail, true);
                        s.ScheduleJob(trigger);
                    }
                    catch (SchedulerException e)
                    {
                        throw e;
                    }
                    finally
                    {
                        s.Start();
                    }
                }
                else
                {
                    s.AddJob(jobDetail, true);
                    s.ScheduleJob(trigger);
                }

                log.Info("Job scheduled");
            }
            catch (Exception e)
            {
                log.Error("Error scheduling job", e);
            }
        }
    }
}
